﻿/**
 * 
 * Copyright 2009, 2010 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace moose.irc.extension {
    /// <summary>
    /// Extension Manager manages the loading of MooseIRC Engine Extensions,
    /// Additionally, it manages the creation and organizing of Extension Actions etc.
    /// </summary>
    static internal class ExtensionManager {
        /// <summary>
        /// Cached command type information
        /// </summary>
        private static Dictionary<ActionTypes, Dictionary<string, Type>> m_action_typecache;

        /// <summary>
        /// Action Extensions Type cache
        /// </summary>
        private static Dictionary<Type, Type> m_extensions_typecache;

        /// <summary>
        /// Cached instances of commands 
        /// </summary>
        private static Dictionary<ActionTypes, Dictionary<string, IrcAction>> m_actions;

        /// <summary>
        /// Initializes Static Members 
        /// </summary>
        static ExtensionManager() {
            m_action_typecache = new Dictionary<ActionTypes, Dictionary<string, Type>>();
            m_action_typecache[ActionTypes.Command] = new Dictionary<string, Type>();
            m_action_typecache[ActionTypes.Response] = new Dictionary<string, Type>();

            m_actions = new Dictionary<ActionTypes, Dictionary<string, IrcAction>>();
            m_actions[ActionTypes.Command] = new Dictionary<string, IrcAction>();
            m_actions[ActionTypes.Response] = new Dictionary<string, IrcAction>();

            m_extensions_typecache = new Dictionary<Type, Type>();
            load_extensions();
        }

        /// <summary>
        /// Attempt to get an action, specify the type, and specify 
        /// the trigger text, to locate an appropriate action. 
        /// </summary>
        /// <param name="type">
        /// A <see cref="ActionTypes"/>
        /// </param>
        /// <param name="trigger">
        /// A <see cref="System.String"/>
        /// </param>
        /// <returns>
        /// Instance of an action, or null if we just dont have an action like this
        /// </returns>
        public static IrcAction get_action(ActionTypes type, string trigger) {
            IrcAction action = null;
            trigger = trigger.ToUpper(); //Convert this to upper case, so we ignore case
            if (m_actions.ContainsKey(type) && m_actions[type].ContainsKey(trigger))
                action = m_actions[type][trigger];
            //This will be null if it has not yet been created
            if (action != null)
                return action;
            //So.. if it's not null, make like a bandit..
            //Otherwise, try and create one
            if (m_action_typecache[type].ContainsKey(trigger)) {
                Type action_type = m_action_typecache[type][trigger];
                action = (IrcAction)action_type.Assembly.CreateInstance(action_type.ToString());
                m_actions[type][trigger] = action;
                return m_actions[type][trigger];
            } else {
                //If no definition exists, we just cant do it..
                return null;
            }
        }

        /// <summary>
        /// Get Irc Action Extension
        /// </summary>
        /// <param name="type">
        /// A <see cref="ActionTypes"/>
        /// </param>
        /// <param name="base_action_type">
        /// A <see cref="Type"/>
        /// </param>
        /// <returns>
        /// Instance of an action, or null if we just dont have an action like this
        /// </returns>
        public static IrcAction get_extension(Type base_action_type, IrcAction base_action) {
            if (m_extensions_typecache.ContainsKey(base_action_type)) {
                Type extension_type = m_extensions_typecache[base_action_type];
                m_extensions_typecache.Remove(base_action_type); //We shouldnt need this any more, as we've already extended that action
                return (IrcAction)extension_type.Assembly.CreateInstance(
                                                                         extension_type.ToString(),
                                                                         true, //Put this in here, to ignore case, in case the creator of extension.. made.. mistake.. 
                                                                         BindingFlags.Default,
                                                                         null,
                                                                         new object[] { base_action },
                                                                         null, null);
            } else {
                return null;
            }
        }

        /// <summary>
        /// Loads Extensions,
        /// loads each assembly, and scans through types, then caches each type
        /// that is useful to us.
        /// </summary>
        private static void load_extensions() {
            Assembly asm = null;
            foreach (string assembly_path in Directory.GetFiles(Environment.CurrentDirectory, "*.dll")) {
                try {
                    asm = Assembly.LoadFile(assembly_path);
                } catch (Exception caught) {
                    Console.Error.Write(caught.Message);
                    Console.Error.Write("");
                    Console.Error.Write(caught.StackTrace);
                    Console.Error.WriteLine();
                    continue;
                }
                if (asm == null)
                    continue;

                load_extension_actions(asm);

            }
            return;
        }

        /// <summary>
        /// Load Extension Actions, 
        /// to simplify this method, I've handed the Action Type in as a parameter,
        /// this will mean that this method will be called twice, once as a Command, and
        /// once as a Response
        /// </summary>
        /// <param name="extension">
        /// A <see cref="Assembly"/>
        /// The actual extension assembly
        /// </param>
        /// <param name="type">
        /// A <see cref="ActionTypes"/>
        /// Action Type
        /// </param>
        private static void load_extension_actions(Assembly extension) {
            object[] attributes = null;
            foreach (Type action in extension.GetTypes()) {
                attributes = action.GetCustomAttributes(typeof(ActionAttribute), false);
                if (attributes.Length == 1) {
                    //There can be only one action attribute
                    cache_extension_action(action);
                    //Cache it
                }
            }
            return;
        }

        /// <summary>
        /// Cache a specific Action Type 
        /// </summary>
        /// <param name="action">
        /// A <see cref="Type"/>
        /// </param>
        /// <param name="action_attribute">
        /// A <see cref="ActionAttribute"/>
        /// </param>
        private static void cache_extension_action(Type action) {
            object[] attributes = action.GetCustomAttributes(typeof(ActionAttribute), false);
            ActionAttribute action_attribute = (ActionAttribute)attributes[0];

            //I'm not going to check this, because I'm only calling it from one checked place...
            foreach (object trigger in action.GetCustomAttributes(typeof(TriggerAttribute), false)) //Modified this, so the trigger is not case sensitive
                m_action_typecache[action_attribute.Type][((TriggerAttribute)trigger).TriggerText.ToUpper()] = action;

            foreach (object extends in action.GetCustomAttributes(typeof(ExtendsAttribute), false))
                m_extensions_typecache[((ExtendsAttribute)extends).BaseType] = action;
            return;
        }

    }
}
